// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <map>
#include <string>
#include <vector>

#include "coin2/exchange/base/executor_util/market_ts.h"
#include "coin2/exchange/base/feed/processor.h"
#include "coin2/exchange/binance/symbology/product.h"
#include "coin2/exchange/binance_base/api_util/auth_key.h"
#include "coin2/exchange/order/binance.pb.h"

namespace coin2::exchange::binance::feed_v1 {
namespace impl {

using coin2::exchange::feed::WebsocketConfig;

class BinanceRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit BinanceRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}

  std::vector<std::pair<std::string, std::string>> GenerateHeader(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    auto it = std::find(channels.begin(), channels.end(), "wallet");
    if (it != channels.end()) {
      static BinanceRestAuth auth(config_->key_filepaths(0), "", "", "");
      return auth.GetHeader();
    }
    return {};
  }

  std::vector<std::string> GenerateRequests(
    const std::vector<std::string>& channels,
    const std::vector<std::string>& symbols,
    const std::map<std::string, std::string>& channel_templates,
    const std::string& request_template) override {
    std::vector<std::string> reqs;

    std::vector<std::string> ch = channels;
    auto it = std::find(ch.begin(), ch.end(), "nav");
    if (it != ch.end()) {
      auto timestamp = MarketTimestampProvider::GetTimestamp();
      for (auto& symbol : symbols) {
        auto product = symbology::BinanceProduct::FromNativeStr(symbol, timestamp);
        for (auto nav_type : {"UP", "DOWN"}) {
          if (boost::algorithm::ends_with(product.base().currency(), nav_type)) {
            std::string c = channel_templates.at("nav");
            boost::replace_all(c, "{}", product.nav_native_symbol());
            nlohmann::json j;
            j.push_back(c);
            std::string s = request_template;
            boost::replace_all(s, "{json_list}", j.dump());
            reqs.push_back(s);
          }
        }
      }
      ch.erase(it);
    }

    // wallet
    it = std::find(ch.begin(), ch.end(), "wallet");
    if (it != ch.end()) {
      CHECK_GT(config_->key_filepaths_size(), 0);
      static BinanceRestAuth auth(config_->key_filepaths(0), "", "", "");
      coin2::exchange::order::BinanceRestParams params;
      params.set_recv_window(5000);
      UrlParamsBuilder builder;
      builder.PutGet("recvWindow", params.recv_window());
      builder.PutGet("timestamp", std::to_string(GetCurrentTimestamp() / 1000000));
      reqs.push_back(auth.CreateSignature(&builder));
      ch.erase(it);
    }

    // kline
    std::vector<std::string> ch_kline;
    auto iter = ch.begin();
    while (iter != ch.end()) {
      if (StringStartsWith(*iter, "kline_rest")) {
        ch_kline.push_back(*iter);
        iter = ch.erase(iter);
      } else {
        iter++;
      }
    }

    auto timestamp = MarketTimestampProvider::GetTimestamp();
    if (!ch_kline.empty()) {
      boost::posix_time::ptime round_time = TimestampRoundHour(timestamp);
      for (auto& ch_k : ch_kline) {
        std::string c = channel_templates.at(ch_k);
        auto interval_time = GetInterval(c);
        for (auto& symbol : symbols) {
          std::vector<std::pair<int64_t, int64_t>> durations;
          bool long_query = base::feed::RequestGenerator::CheckLongQuery(
              round_time, symbol, ch_k, &last_long_query_time_);
          if (long_query) {
            durations = base::feed::RequestGenerator::GetDurations(
                config_->kline_length(), config_->kline_start(), config_->kline_end(),
                interval_time, 900);
          } else {
            // query small data
            durations = base::feed::RequestGenerator::GetDurations(
                "3", "", "",interval_time, 900);
          }

          for (const auto& d : durations) {
            std::string c_copy = c;
            // rest api symbol is upper
            boost::replace_all(c_copy, "{}", StringToUpperCopy(symbol));
            boost::replace_all(c_copy, "{end}", std::to_string(d.second / 1'000'000L));
            boost::replace_all(c_copy, "{start}", std::to_string(d.first / 1'000'000L));
            reqs.emplace_back(c_copy);
          }
        }
      }
    }

    std::vector<std::string> reqs_others = RequestGenerator::GenerateRequests(
        ch, symbols, channel_templates, request_template);
    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());

    return reqs;
  }

 private:
   int64_t GetInterval(const std::string& channel) {
    if (channel.find("interval=1m") != std::string::npos) {
      return (int64_t)60 * 1'000'000'000;
    } else if (channel.find("interval=5m") != std::string::npos) {
      return (int64_t)300 * 1'000'000'000;
    } else if (channel.find("interval=15m") != std::string::npos) {
       return (int64_t)900 * 1'000'000'000;
    } else if (channel.find("interval=1h") != std::string::npos) {
      return (int64_t) 60 * 60 * 1'000'000'000;
    }  else if (channel.find("interval=1d") != std::string::npos) {
      return (int64_t)24 * 60 * 60 * 1'000'000'000;
    } else {
      THROW() << "Not support new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

  const WebsocketConfig* config_;
};

}  // namespace impl
using impl::BinanceRequestGenerator;
}  // namespace coin2::exchange::binance::feed_v1
